package com.example.portablefortheelderlybackground.sevice.Impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.portablefortheelderlybackground.config.redis.redisLock;
import com.example.portablefortheelderlybackground.mapper.orderMapper;
import com.example.portablefortheelderlybackground.pojo.order.order;
import com.example.portablefortheelderlybackground.pojo.shop.shop;
import com.example.portablefortheelderlybackground.sevice.orderService;
import com.example.portablefortheelderlybackground.sevice.shopService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.example.portablefortheelderlybackground.config.userDetails.UserInfo.getUserInfo;

@Service
public class orderServiceImpl extends ServiceImpl<orderMapper, order> implements orderService {
    //注入shopService
    private final
    shopService shopService;

    public orderServiceImpl(shopService shopService, StringRedisTemplate stringRedisTemplate) {
        this.shopService = shopService;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private final
    StringRedisTemplate stringRedisTemplate;

    @Override
    public Boolean createOrder(String id) {
        //1.先获取当前用户id
        String userId = getUserInfo().getUser().getId();
        //2.判断当前商品是否存在库存
        shop shopById = shopService.getById(id);
        //2.1如果不存在库存
        if (ObjectUtils.isEmpty(shopById) || shopById.getCount() <= 0) {
            return false;
        }
        //2.1.1实现用户一人一单
        //2.1.2 根据用户id和商品id判断是否存在对应的订单信息
        LambdaQueryWrapper<order> orderLambdaQueryWrapper = new LambdaQueryWrapper<>();
        orderLambdaQueryWrapper.eq(order::getUserId, userId).eq(order::getShopId, shopById.getId());
        String isExistOrderJson = stringRedisTemplate.opsForValue().get("order:" + shopById.getId() + ":" + userId);
        List<order> isExistOrder = null;
        if (!StringUtils.hasLength(isExistOrderJson)) {
            isExistOrder = baseMapper.selectList(orderLambdaQueryWrapper);
        } else {
            isExistOrder = JSONUtil.toList(isExistOrderJson, order.class);
        }
        /* *
         * 如下方式实现的一人一单逻辑,在多线程的情况下,会出现线程安全问题
         */
        if (!ObjectUtils.isEmpty(isExistOrder)) {
            //2.1.3 如果存在对应的订单,则直接返回
            //可以存redis缓存,因为存在订单,用户后续请求可以打到redis中去
            String jsonStr = JSONUtil.toJsonStr(isExistOrder);
            stringRedisTemplate.opsForValue().set("order:" + shopById.getId() + ":" + userId, jsonStr, 60, TimeUnit.SECONDS);
            return false;
        }
        /* *
         * 通过redis的锁机制实现分布式锁
         */
        // 对用户进行上锁
        redisLock redisLock = new redisLock(userId, stringRedisTemplate);
        boolean lock = redisLock.lock();
        //判断上锁是否成功
        if (!lock) {
            //如果上锁未成功,则表明该用户已占用该锁
            return false;
        }
        boolean update;
        try {
            //2.2如果存在库存
            UpdateWrapper<shop> updateWrapper = new UpdateWrapper<>();
            /* *
             * 如果按照如下方式进行订单创建,会出现超卖的问题,即卖出的限定的数量
             *  这是因为在多线程的情况下,线程并行处理导致的
             *  1.可以通过对方法加锁解决,不过这样处理效率很低,线程是串行处理的
             *  2.通过添加乐观锁,只要不存在安全问题，就不加锁,可以通过版本号实现
             *  在数据表中添加版本号相关字段,每次更新对版本号进行判断,即更新前后是否相等
             */
            //updateWrapper.setSql("count=count-1").eq("id", id);
            /* *
             * 下面的乐观锁的实现通过对count进行是否大于0的判断,
             * 每次更新前都会判断count是否大于0,因为数据库执行更新语句是线程安全的
             * 主要还是利用到数据库的增删改是线程安全的性质,实现乐观锁
             */

            updateWrapper.setSql("count=count-1").eq("id", id).gt("count", 0);
            update = shopService.update(updateWrapper);
            //3.创建订单
            if (update) {
                order order = new order();
                order.setId(IdUtil.simpleUUID());
                order.setShopId(id);
                order.setUserId(userId);
                baseMapper.insert(order);
            }
        } finally {
            //释放锁
            redisLock.unlock();
        }
        return update;
    }
}
